<?php
/**
 * Copyright (c) 2006-2008, Julien PORTALIER
 * http://featherphp.googlecode.com/
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 */

/**
 * Generic Object Record.
 * 
 * @todo Move validation logic from ActiveRecord to Object.
 * @todo Move associations logic from ActiveRecord to Object.
 */
abstract class Object implements Iterator
{
	protected $_name;
	protected $_data         = array();
#	protected $_new_record   = true;
	protected $_associations = array();
	
	abstract function & find();
	abstract function read($id=null, $fields=null);
	abstract function save($validate=true);
	abstract function create($data=null, $validate=true);
	abstract function update($id, $data=null, $validate=true);
	abstract function delete($id=null);
	
	/**
	 * Sets data for current record.
	 * 
	 * @param $data Array
	 * @param $merge Boolean Merge data instead of overwriting it.
	 */
	protected function set(array $data, $merge=false)
	{
		if (!isset($data[$this->_name]))
		{
			# this record only
			if (!$merge)
			{
				#$this->_data = array_intersect_key($data, $this->_fields);
				$this->_data = $data;
			}
			else
			{
				#$this->_data = array_merge($this->_data, array_intersect_key($data, $this->_fields));
				$this->_data = array_merge($this->_data, $data);
			}
		}
		else
		{
			foreach($data as $model => $d)
			{
				if ($model == $this->_name)
				{
					# this record
					$this->set($d);
				}
				elseif (array_key_exists($model, $this->_associations))
				{
					# association
					$this->$model->set($d);
				}
				#else: discard.
			}
		}
	}
	
	/**
	 * Merges some data to actual record. Unlink Object::set()
	 * it doesn't replaces current data, but merges it with
	 * the new one.
	 * 
	 * @param $data Array
	 */
	protected function merge(array &$data)
	{
		$this->set($data, true);
	}
	
	/**
	 * Ignits record to its default values.
	 */
	protected function set_defaults()
	{
		#$this->set(array());
	}
	
	/**
	 * Magical function to check the presence of a field's value.
	 */
	function __isset($attr)
	{
		return isset($this->_data[$attr]);
	}
	
	/**
	 * Magical function to read a field's value.
	 */
	function __get($attr)
	{
		if (array_key_exists($attr, $this->_data)) {
			return $this->_data[$attr];
		}
	}
	
	/**
	 * Magical function to set a field's value.
	 */
	function __set($attr, $value)
	{
		if (strpos($attr, '_') !== 0
			and !array_key_exists($attr, $this->_associations))
		{
			$this->_data[$attr] = $value;
#			if ($attr == $this->_primary_key) {
#				$this->_new_record = !$this->exists($value);
#			}
		}
		else {
			$this->$attr = $value;
		}
	}
	
	/**
	 * Magical function to unset a field's value.
	 */
	function __unset($attr)
	{
		unset($this->_data[$attr]);
	}
	
	/**
	 * Extends a resultset of data (Array) to a resultset of Object.
	 * 
	 * @return Array of Objects.
	 * @param $rs Array Resultset.
	 */
	protected function & __extend_results($rs)
	{
		$class = $this->_name;
		foreach($rs as $i => $data) {
			$rs[$i] = new $class(&$data);
		}
		return $rs;
	}
	
	
	// ITERATOR
	
	function rewind()  { reset($this->_data); }
	function current() { return current($this->_data); }
	function key()     { return key($this->_data); }
	function next()    { return next($this->_data); }
	function valid()   { return (false !== current($this->_data)); }
	
	
	// SERIALIZATION
	
	/**
	 * @todo requires tests!
	 */
	function & __sleep()
	{
		return $this->_data;
	}
	
	/**
	 * @todo requires tests!
	 */
	function __wakeup()
	{
		$this->__construct();
	}
	
	
	// TYPECAST
	
	function __toString()
	{
		ob_start();
		var_dump($this->_data);
		return ob_get_clean();
	}
}
?>